﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Software41.Tracking.BAM
{
    [Serializable]
    public abstract class InfoBase
    {
        

        internal const string ACTIVITY_DATA_NAME = "Name";
        internal const string ACTIVITY_DATA_BEGIN = "Begin";
        internal const string ACTIVITY_DATA_END = "End";
        internal const string ACTIVITY_DATA_SUCCESS = "Success";
        internal const string ACTIVITY_MSG_BEGIN_DATA = "BeginData";
        internal const string ACTIVITY_MSG_END_DATA = "EndData";

        public ActivityStateType State { get; protected set; }

        protected ActivityType CurrentActivityType { get; set; }
        protected ActivityType? ParentActivityType { get; set; }
        protected string ActivityNameData { get; set; }
        protected string InfoId { get; set; }
        protected string ParentInfoId { get; set; }
        protected string Name { get; set; }
        protected IEventStream EventStream { get; set; }
        protected List<LogDetail> DependentLogDetails = new List<LogDetail>();
        protected ExceptionDetail ExceptionDetail { get; set; }


        #region Begin Methods
        public string Begin()
        {
            return this.BeginInternal(null, null);
        }

        public virtual string Begin(string messageDataName, string messageData)
        {
            if (string.IsNullOrEmpty(messageDataName))
                throw new ArgumentNullException("messageDataName");

            if (string.IsNullOrEmpty(messageData))
                throw new ArgumentNullException("messageData");

            return this.BeginInternal(messageDataName, messageData);
        }

        private string BeginInternal(string messageDataName, string messageData)
        {
            if (string.IsNullOrEmpty(this.InfoId))
                this.InfoId = Guid.NewGuid().ToString();

            this.TraceBeginEvent(messageDataName, messageData);

            this.EventStream.BeginActivity(this.CurrentActivityType.ToString(), this.InfoId);

            if (this.ParentActivityType.HasValue)
                this.EventStream.AddRelatedActivity(this.CurrentActivityType.ToString(), this.InfoId,
                                                                this.ParentActivityType.ToString(), this.ParentInfoId);

            this.AddRelatedDocument(messageDataName, messageData, ACTIVITY_MSG_BEGIN_DATA);


            this.UpdateActivity(ACTIVITY_DATA_NAME, this.Name);
            this.UpdateActivity(ACTIVITY_DATA_BEGIN, DateTime.UtcNow);

            this.State = ActivityStateType.Initialized;

            

            return this.InfoId;
        }
        #endregion

        #region End Methods
        public virtual void End()
        {
            this.EndInternal(true, null, null);
        }

        public virtual void End(string messageDataName, string messageData)
        {
            this.End(true, messageDataName, messageData);
        }

        public void End(bool success)
        {
            this.EndInternal(success, null, null);
        }

        public virtual void End(bool success, string messageDataName, string messageData)
        {
            if (string.IsNullOrEmpty(messageDataName))
                throw new ArgumentNullException("messageDataName");

            if (string.IsNullOrEmpty(messageData))
                throw new ArgumentNullException("messageData");

            this.EndInternal(success, messageDataName, messageData);
        }

        internal virtual void EndInternal(bool success, string messageDataName, string messageData)
        {
            this.UpdateActivity(ACTIVITY_DATA_SUCCESS, success);
            this.UpdateActivity(ACTIVITY_DATA_END, DateTime.UtcNow);

            this.AddRelatedDocument(messageDataName, messageData, ACTIVITY_MSG_END_DATA);

            this.AttemptCloseExceptionInfo();
            this.EndDependentLogDetails();

            this.State = ActivityStateType.Complete;

            this.EventStream.EndActivity(this.CurrentActivityType.ToString(), this.InfoId);

            this.TraceEndEvent(true, messageDataName, messageData);
        }

        #endregion

        #region ExceptionInfo Mgmt

        public virtual void AddException(Exception ex)
        {
            this.ExceptionDetail = new ExceptionDetail(ex, this.EventStream);
        }

        public void AddExceptionActionTaken(string descOfActionTaken)
        {
            if (this.ExceptionDetail == null)
                throw new Exception("No exception available.  Please call AddException() to add an exception to associate this action to.");


            this.ExceptionDetail.UpdateActionsTaken(descOfActionTaken);

        }

        private void AttemptCloseExceptionInfo()
        {
            if ((this.ExceptionDetail != null) && (this.ExceptionDetail.State == ActivityStateType.Started))
                this.ExceptionDetail.End();

        }


        #endregion

        #region AddDetail Mgmt
        public virtual LogDetail AddLogDetail()
        {
            var logDetail = new LogDetail(null, this.EventStream);
            this.DependentLogDetails.Add(logDetail);
            return logDetail;

        }

        private void EndDependentLogDetails()
        {
            this.DependentLogDetails.ForEach(a =>
            {
                if (a.State == ActivityStateType.Started)
                {
                    //something happened that we did not call end on this 
                    a.End();
                }

            });
        }

        #endregion

        #region Helpers

        protected void UpdateActivity(string ItemName, object ItemValue)
        {
            this.EventStream.UpdateActivity(this.CurrentActivityType.ToString(), this.InfoId, ItemName, ItemValue);
            this.TraceUpdateActivity(ItemName, ItemValue);
        }

        protected void AddRelatedDocument(string messageDataName, string messageData, string referenceName)
        {
            if ((!string.IsNullOrEmpty(messageDataName)) && (!string.IsNullOrEmpty(messageData)))
            {

                string referenceType = string.Format("{0}Data-{1}", this.CurrentActivityType, referenceName);

                this.EventStream.AddReference(this.CurrentActivityType.ToString(), this.InfoId,
                                            referenceType, referenceName,
                                            messageDataName, messageData);


                var msgDataLink = BAMLinkedDocUrlGenerator.GenerateLinkedDocUrl(this.CurrentActivityType.ToString(), this.InfoId, 
                                                                                    referenceType);

                this.EventStream.AddReference(this.CurrentActivityType.ToString(), this.InfoId,
                                                BAMLinkedDocUrlGenerator.BAM_DOCUMENT_URL,
                                                messageDataName + " " + referenceName, msgDataLink);

            }
        }

        internal abstract void TraceBeginEvent(string messageDataName, string messageData);

        internal abstract void TraceEndEvent(bool successful, string messageDataName, string messageData);

        internal abstract void TraceUpdateActivity(string itemName, object itemValue);


        #endregion


    }
}
